package bj.actor

import scala.actors.Actor
import scala.actors.OutputChannel
import bj.card.Hand
import bj.hkeeping.Ok
import bj.card.Card
import bj.util.Logs
import bj.hkeeping.Broke

object BotPlayer {
  // constant definitions for possible actions
  val STAND = 1
  val HIT = 2
  val DOUBLE = 3
  val SPLIT = 4
  val SURRENDER = 5
 
  // List[List[Int]] to reflect basic strategy for a hand of pair
  val strategyPair: List[List[Int]] = 
    List(
      List(0),  //0 - not possible
      List(0,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT),  //A-A
      List(0,HIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,HIT,HIT,HIT,HIT),  //2-2
      List(0,HIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,HIT,HIT,HIT,HIT),  //3-3
      List(0,HIT,HIT,HIT,HIT,SPLIT,SPLIT,HIT,HIT,HIT,HIT,HIT),          //4-4
      List(0,HIT,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,HIT,HIT), //5-5
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),              //6-6
      List(0,HIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,HIT,HIT,HIT,HIT),  //7-7
      List(0,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT), //8-8 (always split)
      List(0,STAND,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,STAND,SPLIT,SPLIT,STAND,STAND), //9-9
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND), //10-10 could be 10,J,Q,K
      List(0,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT,SPLIT)  //A-A (always split)
    )
  
  // List[List[Int]] to reflect basic strategy for a soft hand
  val strategyAce: List[List[Int]] = 
    List(
      List(0),  //A-0 - not possible
      List(0),  //A-1 - could be A-A, but that is taken in pairs list
      List(0,HIT,HIT,HIT,HIT,DOUBLE,DOUBLE,HIT,HIT,HIT,HIT,HIT),       //A-2
      List(0,HIT,HIT,HIT,HIT,DOUBLE,DOUBLE,HIT,HIT,HIT,HIT,HIT),       //A-3
      List(0,HIT,HIT,HIT,DOUBLE,DOUBLE,DOUBLE,HIT,HIT,HIT,HIT,HIT),    //A-4
      List(0,HIT,HIT,HIT,DOUBLE,DOUBLE,DOUBLE,HIT,HIT,HIT,HIT,HIT),    //A-5
      List(0,HIT,HIT,DOUBLE,DOUBLE,DOUBLE,DOUBLE,HIT,HIT,HIT,HIT,HIT), //A-6
      List(0,HIT,STAND,DOUBLE,DOUBLE,DOUBLE,DOUBLE,STAND,STAND,HIT,HIT,HIT), //A-7
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND), //A-8
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND), //A-9
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND)  //A-10 (BLACKJACK!)
    )

  // List[List[Int]] to reflect basic strategy for a hand of size 2
  val strategyNormal: List[List[Int]] =
    List(
      List(0),
      List(0),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,DOUBLE,DOUBLE,DOUBLE,DOUBLE,HIT,HIT,HIT,HIT,HIT), //9
      List(0,HIT,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,HIT,HIT), //10
      List(0,HIT,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,HIT), //11
      List(0,HIT,HIT,HIT,STAND,STAND,STAND,HIT,HIT,HIT,HIT,HIT), //12
      List(0,HIT,STAND,STAND,STAND,STAND,STAND,HIT,HIT,HIT,HIT,HIT), //13
      List(0,HIT,STAND,STAND,STAND,STAND,STAND,HIT,HIT,HIT,HIT,HIT), //14
      List(0,HIT,STAND,STAND,STAND,STAND,STAND,HIT,HIT,HIT,SURRENDER,HIT), //15
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,HIT,HIT,SURRENDER,SURRENDER,SURRENDER), //16
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND), //17+
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND),
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND),
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND),
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND)
    )
  
  // List[List[Int]] to reflect basic strategy for a hand of three or more cards
  val strategyThreePlus: List[List[Int]] =
    List(
      List(0),
      List(0),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT),
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT), //9
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT), //10
      List(0,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT,HIT), //11
      List(0,HIT,HIT,HIT,STAND,STAND,STAND,HIT,HIT,HIT,HIT,HIT), //12
      List(0,HIT,STAND,STAND,STAND,STAND,STAND,HIT,HIT,HIT,HIT,HIT), //13
      List(0,HIT,STAND,STAND,STAND,STAND,STAND,HIT,HIT,HIT,HIT,HIT), //14
      List(0,HIT,STAND,STAND,STAND,STAND,STAND,HIT,HIT,HIT,HIT,HIT), //15
      List(0,HIT,STAND,STAND,STAND,STAND,STAND,HIT,HIT,HIT,HIT,HIT), //16
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND), //17+
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND),
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND),
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND),
      List(0,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND,STAND)
    )
      

}
class BotPlayer (playerName: String, beginningBankroll: Double,betAmt: Double) extends Player(playerName,beginningBankroll,betAmt) with Logs {
  /** 
   * This method receives messages 
   */
  override def act {
    loop {
      react {
        // Receives message to initiate a player
        case Go =>
          debug(this+" received Go placing bet = "+betAmt+" from bankroll = "+bankroll)
          bet
          
        // Receives the dealer's up-card
        case Up(card) =>
          debug(this + " received dealer's up card = " + card)
          // saves the upcard
          this.upcard = card
          addToCount(card)

        // Receives a message indicating the player should place a bet for the next hand
        case PlaceBet =>
          debug(this +" placing bet")
          computeBet(sender)
        
        // Receives a message indicating the player should act
        case MyTurn =>
          debug(this + " was notified that it is time to act")
          play(upcard)

        // Receives a card from the dealer
        case DealtCard(card,index) =>  
          addToCount(card)
          hitMe(card,index)
        
        // Receives the first card after a requested split
        case SplitCard(card) =>
          processSplit(card)
        
        // Receives broke message
        case Broke =>
          debug(this+ " received BROKE")
          
        // Receives message about card dealt to other player
        case Observe(card,player,index) =>
          debug(this+" observed: "+card)
          addToCount(card)
        
        // Receives message about double down (horizontal) card to other player
        case ObserveH(card,player,index) =>
          debug(this+" observed: "+card)
          addToCount(card)

        // Receives dealer's hole card
        case Hole(card) =>
          debug(this+" observed dealer Hole card " + card )
          addToCount(card)
          
        // Receives dealer's hit cards
        case DealerCard(card) =>
          debug(this+" observed dealer card " + card)
          addToCount(card)

        // Receives the table number I've been assigned to
        case TableNumber(tid : Int) =>
          debug(this+" received table assignment tid = "+tid)
          assign(tid)
        
        // Receives message about game outcome  
        case Win(gain) =>
          val won = lastBet * gain       
          currentBankroll += won          
          
          debug(this+" received WIN " + won + " new bankroll = "+bankroll)
        
        // Receives message about game outcome  
        case Lose(gain) =>
          val lost = lastBet * gain          
          currentBankroll += lost

          debug(this+" received LOSE " + lost + " new bankroll = "+bankroll)          
        
        // Receives message about game outcome  
        case Push(gain) =>
          debug(this+" received PUSH bankroll = "+bankroll)

        // Receives message from dealer offering insurance
        case OfferInsurance =>
          debug(this+ "Dealer has Ace showing and is offering insurance")         
          processInsurance(sender)

        // Receives message from dealer that insurance betting is closed
        case InsuranceClosed =>
          debug(this+ "Insurance is now closed")

        // Receives message from dealer that the dealer was dealt blackjack
        case DealerBJ =>
          debug(this + "dealer got a blackjack)" )

        // Receives message from dealer with current limits for the table
        case MinMaxBets(min,max) =>
          minBet = min
          maxBet = max

        // Receives message from dealer to clear all hand data
        case ResetHand =>
          val h = new Object with Hand
          hand = List(h)
          handIndex = 0

        // Receives message from dealer that a split occurred (for GUI)
        case ObserveSplit(seat) =>
          debug(this + " observed a split at seat " + seat)
          
        // Receives an ACK
        case Ok =>
          debug(this + " received Ok")

        // Receives something completely from left field
        case dontKnow =>
          // Got something we REALLY didn't expect
          debug(this+" received unexpected: "+dontKnow)
      }
    }
  }
  /**
   * This method sends a message to the dealer if the player will buy insurance
   * @param sender The dealer's mailbox 
   */
  def processInsurance (sender: OutputChannel[Any]) {
    // take no action because bot will never buy insurance
  }
  
  /**
   * This method handles the internal setup needed for a split
   * @param card The card being dealt to the first hand
   */
  def processSplit(card: Card) {
    // save the original hand
    val originalCards = hand(0).cards
    // append a new empty hand to the List[Object with Hand]
    hand = hand ::: List(new Object with Hand)
    // first hand contains the original first card plus new card
    hand(0).cards = List(originalCards(0),card)
    // second hand has only one card right now - the original second card
    hand(1).cards = List(originalCards(1))

    // set flags
    hand(0).didSplit = true
    hand(1).didSplit = true

    // play the first hand
    play(upcard)
  }
  
  /**
   * This method keeps track of the count and cards seen
   * @param card The newly dealt card
   */
  def addToCount(card : Card) {
    if (card.value == 10 || card.ace) {
      count = count - 1
    }
    else if (card.value < 7) {
        count = count + 1
    }
    cardsSeen += 1
  }

  /**
   * This method computes the bet and sends to the dealer
   * @param sender The dealer's mailbox
   */
  def computeBet(sender : OutputChannel[Any]) : Unit = {
    val newBet = getBetFromCount
    sender ! NewBet(pid,newBet)
    lastBet = newBet
    moneyOnTable = newBet
  }
  
  /**
   * This message computes the true count and returns an appropriate bet
   */
  def getBetFromCount: Double = {
    // get the number of cards unseen
    var cardsRemaining: Int = DECKS_IN_SHOE*52 - cardsSeen
    if (cardsRemaining == 0) // do not allow divide by 0
      cardsRemaining = 1
      
    // true count is the running count divided by the number of decks left
    val trueCount: Double = count/((cardsRemaining)/52)
    var myBet = 0.0
    
    // if the count is sufficiently negative, only bet the minimum
    if (trueCount < -1)
      myBet = minBet
    // if the count is close to 0, bet the normal bet
    else if (trueCount <= 1)
      myBet = betAmt
    // otherwise the count is in my favor and I want to bet more
    else {
      val scale: Int = ((maxBet - betAmt)/DECKS_IN_SHOE).toInt
      myBet = betAmt + (trueCount*scale).toInt    
    }
    // return value
    myBet
  }

  /**
   * Processes hit request.
   * @param dealer Dealer's mailbox
   * @param upcard Dealer's up-card
   */  
  def hitMe(card : Card, index: Int) {
    // Hit my hand with this card
    if (hand.size == 0) {
      var h = new Object with Hand
      hand = hand ::: List(h)
    }
    hand(handIndex).hit(card)
    
    debug(this + " received card " + card + " hand sz = " + hand(handIndex).cards.size + " value = " + hand(handIndex).value)          

    // If I've received more than two cards, the extras must be in
    // response to my requests
    if (hand(handIndex).cards.size > 2 && hand(handIndex).value <= 21) { // if I have more than two cards but less than 21
      if(hand(handIndex).doubled) { // and I doubled previously
        if (hand(handIndex).didSplit) { // and also split
          handIndex += 1 // move on to the next hand
        }
      }
      else { // otherwise play the card I was just dealt
        play(upcard)
      }
    }
  }
  
  /**
   * Processes the dealer's upcard.
   * @param upcard Dealer's up-card
   */
  def play(upcard : Card) {
    this.upcard = upcard

    // Compute my play strategy
    val request = analyze(upcard)

    debug(this + " request = " + request)

    // Don't send a request if we break since
    // deal will have moved on
    if(hand.size == 1) {
      if(!hand(0).broke) {
        sender ! request
      }
    }
    else if(hand(1).cards.size==1) {
      if(!hand(0).broke) {
        sender ! request 
      }
    }
    else if(!hand(1).broke) {
      sender ! request
    }    
  }
  
  /**
   * This method determines what action the player will take given the current state of hands
   * @param upcard The dealer's upcard
   */
  def analyze(upcard: Card) : Request = {
    var result = 0
    if (handIndex > 1)
      handIndex = 0
      
    // put the current hand in memory
    val thisHand = hand(handIndex)
    
    // determine which List[List[Int]] to use to determine action
    if (thisHand.cards.size==2 && !thisHand.didSplit) { // we have exactly two cards
      if(thisHand.cards(0).rank==thisHand.cards(1).rank) { // we have a pair
        result = BotPlayer.strategyPair(thisHand.cards(0).value)(upcard.value) 
      }
      else if (thisHand.cards(0).ace||thisHand.cards(1).ace) { // we have an ace
        result = BotPlayer.strategyAce(thisHand.cards(0).value+thisHand.cards(1).value-1)(upcard.value)
      }
      else {
        result = BotPlayer.strategyNormal(thisHand.value)(upcard.value)
      }
    }
    else if (thisHand.value <=21) { // we have three or more cards
      result = BotPlayer.strategyThreePlus(thisHand.value)(upcard.value)
    }
    else result=BotPlayer.STAND
    
    if((result==BotPlayer.DOUBLE || result==BotPlayer.SPLIT) && (bankroll < (moneyOnTable + lastBet))) { // if we don't have enough money to split or double down
      result = BotPlayer.strategyThreePlus(thisHand.value)(upcard.value)
    }
    
    if(result==BotPlayer.SPLIT && thisHand.didSplit) { //if the basic strategy is a re-split, get another action
      result = BotPlayer.strategyThreePlus(thisHand.value)(upcard.value)
    }
    
    Thread.sleep(1500) // sleep to simulate "thinking"
    if (result==BotPlayer.STAND) {
      if(thisHand.didSplit) {
        handIndex +=1
        return Stay(pid,handIndex-1)
      }
      else return Stay(pid,handIndex)
    }
    else if (result==BotPlayer.HIT) { // we are hitting
      return Hit(pid,handIndex)
    }
    else if (result==BotPlayer.DOUBLE) { // we want to double down
      thisHand.doubled = true
      moneyOnTable += lastBet
      return DoubleDown(pid,handIndex)
    }
    else if (result==BotPlayer.SPLIT) { // we want to split
      thisHand.didSplit = true
      moneyOnTable += lastBet
      handIndex = 0
      return Split(pid)
    }
    else if (result==BotPlayer.SURRENDER) { // we want to surrender
      if(thisHand.didSplit == true) {
        handIndex += 1
        moneyOnTable -= (lastBet*0.5)
        return Surrender(pid,handIndex-1)
      }
      else return Surrender(pid,handIndex)
    }
    else return Unknown(pid) // default is unreachable and here only to guarantee return type
  }  
}